// BlockQueue.hpp
#pragma once
#include <iostream>
#include <queue>
#include <pthread.h>

template <class T>
class BlockQueue
{
    static const int defaultnum = 10;
public:
    BlockQueue(int maxcap = defaultnum)
        :_maxcap(maxcap)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_c_cond, nullptr);
        pthread_cond_init(&_p_cond, nullptr);
        _low_water = maxcap / 3;
        _high_water = maxcap * 2 / 3;
    }
    T pop()
    {
        pthread_mutex_lock(&_mutex);
        // 你想消费，就直接能消费吗？不是，得先确保消费条件满足
        // 必须做到防止线程被伪唤醒的情况，if判断改while判断
        while (_q.size() == 0)
        {
            // 可能存在伪唤醒
            pthread_cond_wait(&_c_cond, &_mutex);
        }

        T out = _q.front();
        _q.pop();
        // 消费者刚pop了数据，而且锁还没释放，此时队列里一定有空间，所以由消费者来叫醒生产者
        /*if (_q.size() < _low_water)*/ pthread_cond_signal(&_p_cond);
        pthread_mutex_unlock(&_mutex);
        return out;
    }
    void push(const T &in)
    {
        pthread_mutex_lock(&_mutex);
        // 你想生产，就直接能生产吗？不是，得先确保生产条件满足
        // 我们怎么知道需要让一个线程去休眠了呢？一定是临界资源不就绪，临界资源也是有状态的
        // 我们怎么知道临界资源是否就绪呢？判断出来的
        // 判断本身是访问临界资源吗？是的，所以判断必须在加锁之后
        while (_q.size() == _maxcap)
        {  
            // 正因为判断必须在加锁之后，所以pthread_cond_wait必须把锁带上，
            // 因为此时线程已经拿到了唯一的一把锁，它必须先释放再去休眠
            pthread_cond_wait(&_p_cond, &_mutex);  // 1.调用的时候，自动释放锁
        }
        // 走到这里时，1.队列没满，2.被唤醒
        _q.push(in);
        // 生产者刚push了数据，而且锁还没释放，此时队列里一定有数据，所以由生产者来叫醒消费者
        /*if (_q.size() > _high_water)*/ pthread_cond_signal(&_c_cond);
        pthread_mutex_unlock(&_mutex);
    }
    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_c_cond);
        pthread_cond_destroy(&_p_cond);
    }

private:
    std::queue<T> _q; // 共享资源
    // int _mincap;
    int _maxcap; // 极值，容量
    pthread_mutex_t _mutex;
    pthread_cond_t _c_cond;  // 消费者等待队列
    pthread_cond_t _p_cond;  // 生产者等待队列

    int _low_water;
    int _high_water;
};